/**
 * 
 */
package wangjg.commons.bean;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Dalvik jvm
 * @author wangjg
 *
 */
public class ReflectionUtil {
	
	public static Class getPropertyType(Object bean, String property) {
		PropertyDescriptor pd = getPropertyDescriptor(property, bean.getClass());
		if(pd!=null){
			return pd.getReadMethod().getReturnType().getClass();
		}
		return null;
	}
	
	public static Object getPropertyValue(Object bean, String property){
		try {
			PropertyDescriptor pd = getPropertyDescriptor(property, bean.getClass());
			Method method = pd.getReadMethod();
			return method.invoke(bean);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static void setPropertyValue(Object bean, String property, Object value){
		try {
			PropertyDescriptor pd = getPropertyDescriptor(property, bean.getClass());
			Method method = pd.getWriteMethod();
			method.invoke(bean, value);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void traverseProperties(Class clazz, Object bean, boolean ignoreReadOnly, Callback handle) throws Exception{
		List<PropertyDescriptor> pds = getPropertyDescriptor(clazz);
        for(PropertyDescriptor prop:pds){
        	Method writeMethod = prop.getWriteMethod();
        	if(ignoreReadOnly && writeMethod==null){
        		continue;
        	}
        	Method readMethod = prop.getReadMethod();
        	Object pv = null;
			try {
				if(readMethod!=null && bean!=null){
					pv = readMethod.invoke(bean, Empty.ARRAY);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
        	handle.handle(clazz, writeMethod, readMethod, prop.getName(), pv);
        }
	}
	
	public static void traverseProperties(Class clazz, Object[] beanArray, boolean ignoreReadOnly, Callback handle){
		List<PropertyDescriptor> pds = getPropertyDescriptor(clazz);
	    for(PropertyDescriptor prop:pds){
	    	Method writeMethod = prop.getWriteMethod();
	    	if(ignoreReadOnly && writeMethod==null){
	    		continue;
	    	}
	    	Method readMethod = prop.getReadMethod();
	    	Object[] pvArray = new Object[beanArray.length];
	    	for(int i=0;i<beanArray.length;i++){
	    		Object bean = beanArray[i];
	        	Object pv = null;
				try {
					if(readMethod!=null && bean!=null){
						pv = readMethod.invoke(bean, Empty.ARRAY);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				pvArray[i] = pv;
	    	}
	    	handle.handle(clazz, writeMethod, readMethod, prop.getName(), pvArray);
	    }
	}
	
	private static PropertyDescriptor getPropertyDescriptor(String property, Class clazz){
		List<PropertyDescriptor> pds = getPropertyDescriptor(clazz);
		for(PropertyDescriptor pd:pds){
			if(pd.getName().equals(property)){
				return pd;
			}
		}
		return null;
	}

	private static final Map<Class,List<PropertyDescriptor>> pdMap = new HashMap<Class,List<PropertyDescriptor>>();
	
	private static List<PropertyDescriptor> getPropertyDescriptor(Class clazz){
		List<PropertyDescriptor> result = pdMap.get(clazz);
		if(result != null){
			return result;
		}
		result = new ArrayList<PropertyDescriptor>();
		pdMap.put(clazz, result);
		
		Map<String,Method> readMap = new HashMap<String,Method>();
		Map<String,Method> writeMap = new HashMap<String,Method>();
		List<Method> mlist = getDeclaredMethods(clazz);
		for(Method m : mlist){
			if (!Modifier.isPublic(m.getModifiers()) || Modifier.isStatic(m.getModifiers())) {
				continue;
			}
			Class<?>[] mpt = m.getParameterTypes();
			int mpl = mpt.length;
			
			if((m.getName().startsWith("get")||m.getName().startsWith("is")) && mpl==0 && m.getReturnType()!=null){
				readMap.put(m.getName()+'_'+m.getReturnType().getName(), m);
			}
			
			if(m.getName().startsWith("set") && mpl==1){
				writeMap.put(m.getName()+'_'+mpt[0].getName(), m);
			}
		}
		CodeStringUtil cu = new CodeStringUtil();
		Set<Entry<String, Method>> entrySet = readMap.entrySet();
		for(Entry<String, Method> entry:entrySet){
			String methodName = entry.getKey();
			Method readMethod = entry.getValue();
			String pname = cu.getPropertyName(methodName);
			
			PropertyDescriptor pd = new PropertyDescriptor();
			pd.setName(pname);
			pd.setReadMethod(readMethod);
			
			result.add(pd);
			
			String wmkey = cu.getSetMethod(pname)+'_'+readMethod.getReturnType().getName();
			Method writeMethod = writeMap.get(wmkey);
			if(writeMethod!=null){
				pd.setWriteMethod(writeMethod);
			}
			
		}
        return result;
	}

	protected static List<Method> getDeclaredMethods(Class clazz) {
		List<Method> list = new ArrayList<Method>();
		for (Class<?> superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) {
			Method[] dms = superClass.getDeclaredMethods();
			for(Method m:dms){
				list.add(m);
			}
		}
		return list;
	}
	
	private static class PropertyDescriptor{
		private String name;
		private Method readMethod;
		private Method writeMethod;
		
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public Method getReadMethod() {
			return readMethod;
		}
		public void setReadMethod(Method readMethod) {
			this.readMethod = readMethod;
		}
		public Method getWriteMethod() {
			return writeMethod;
		}
		public void setWriteMethod(Method writeMethod) {
			this.writeMethod = writeMethod;
		}
		
	}
	
	public static interface Callback{
		void handle(Class clazz, Method writeMethod, Method readMethod, String property, Object pv);
	}
	
	public static void main(String[] args) {

	}

}
